using System;
using System.Runtime.InteropServices;

namespace GameFrame.Deterministic
{
  /// <summary>Represents an 2D axis aligned bounding box (AABB).</summary>
  /// 
  ///             \ingroup MathAPI
  [Serializable]
  [StructLayout(LayoutKind.Explicit)]
  public struct FPBounds2
  {
    public const int SIZE = 32;
    /// <summary>Center of the bounding box.</summary>
    [FieldOffset(0)]
    public FPVector2 Center;
    /// <summary>Extents of the bounding box (half of the size).</summary>
    [FieldOffset(16)]
    public FPVector2 Extents;

    /// <summary>
    /// Gets or sets the maximal point of the box. This is always equal to <see cref="F:Photon.Deterministic.FPBounds2.Center" /> + <see cref="F:Photon.Deterministic.FPBounds2.Extents" />.
    /// Setting this property will not affect <see cref="P:Photon.Deterministic.FPBounds2.Min" />.
    /// </summary>
    public FPVector2 Max
    {
      get => this.Center + this.Extents;
      set => this.SetMinMax(this.Min, value);
    }

    /// <summary>
    /// Gets or sets the minimal point of the box. This is always equal to <see cref="F:Photon.Deterministic.FPBounds2.Center" /> - <see cref="F:Photon.Deterministic.FPBounds2.Extents" />.
    /// Setting this property will not affect <see cref="P:Photon.Deterministic.FPBounds2.Max" />.
    /// </summary>
    public FPVector2 Min
    {
      get => this.Center - this.Extents;
      set => this.SetMinMax(value, this.Max);
    }

    /// <summary>
    /// Create a new Bounds with the given center and extents.
    /// </summary>
    /// <param name="center">Center point.</param>
    /// <param name="extents">Extents (half the size).</param>
    public FPBounds2(FPVector2 center, FPVector2 extents)
    {
      this.Center = center;
      this.Extents = extents;
    }

    /// <summary>
    /// Expand bounds by 0.5 * <paramref name="amount" /> in both directions.
    /// </summary>
    /// <param name="amount"></param>
    public void Expand(FP amount) => this.Extents += new FPVector2(amount * FP._0_50, amount * FP._0_50);

    /// <summary>
    /// Expand bounds by 0.5 * <paramref name="amount" /> in both directions.
    /// </summary>
    /// <param name="amount"></param>
    public void Expand(FPVector2 amount) => this.Extents += amount * FP._0_50;

    public void SetMinMax(FPVector2 min, FPVector2 max)
    {
      this.Extents = (max - min) * FP._0_50;
      this.Center = min + this.Extents;
    }

    /// <summary>
    /// Expand bounds to contain <paramref name="point" /> (if needed).
    /// </summary>
    /// <param name="point"></param>
    public void Encapsulate(FPVector2 point) => this.SetMinMax(FPVector2.Min(this.Min, point), FPVector2.Max(this.Max, point));

    /// <summary>
    /// Expand bounds to contain <paramref name="bounds" /> (if needed).
    /// </summary>
    /// <param name="bounds"></param>
    public void Encapsulate(FPBounds2 bounds)
    {
      this.Encapsulate(bounds.Center - bounds.Extents);
      this.Encapsulate(bounds.Center + bounds.Extents);
    }

    /// <summary>
    /// Returns true if there is an intersection between bounds.
    /// </summary>
    /// <param name="bounds"></param>
    /// <returns></returns>
    public bool Intersects(FPBounds2 bounds) => this.Min.X <= bounds.Max.X && this.Max.X >= bounds.Min.X && this.Min.Y <= bounds.Max.Y && this.Max.Y >= bounds.Min.Y;

    public static unsafe void Serialize(void* ptr, IDeterministicFrameSerializer serializer)
    {
      FPBounds2* fpBounds2Ptr = (FPBounds2*) ptr;
      FPVector2.Serialize((void*) &fpBounds2Ptr->Center, serializer);
      FPVector2.Serialize((void*) &fpBounds2Ptr->Extents, serializer);
    }

    public override int GetHashCode() => (17 * 31 + this.Center.GetHashCode()) * 31 + this.Extents.GetHashCode();
  }
}